package org.cem.security;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.ConfirmationCallback;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.TextOutputCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;

import org.cem.datasource.DbAccess;

//import org.osgi.service.useradmin.User;
//import org.osgi.service.useradmin.UserAdmin;

public class RdbmsLoginModule implements LoginModule {

	private CallbackHandler callbackHandler;
	private boolean success;
	private boolean debug;
	private Subject subject;
	private boolean loggedin;
	
	private String user;

	@Override
	public void initialize(Subject subject, CallbackHandler callbackHandler,
			Map<String, ?> sharedState, Map<String, ?> options) {
		this.subject = subject;
		this.callbackHandler = callbackHandler; 
	}

	@Override
	public boolean login() throws LoginException {
		if (callbackHandler == null)  
		    throw new LoginException("Error: no CallbackHandler available "  
		        + "to garner authentication information from the user");  
		  
		    // Setup default callback handlers.
			ConfirmationCallback confirmationCallback = new ConfirmationCallback(
					ConfirmationCallback.INFORMATION,
					ConfirmationCallback.OK_CANCEL_OPTION,
					ConfirmationCallback.CANCEL);
			TextOutputCallback messageCallback = new TextOutputCallback(
					TextOutputCallback.INFORMATION, "enter credential");
			NameCallback nameCallback = new NameCallback("name");
			PasswordCallback passwordCallback = new PasswordCallback("password",
					false);

			return login(confirmationCallback, messageCallback, nameCallback,
					passwordCallback);
	}

	private boolean login(ConfirmationCallback confirmationCallback,
			TextOutputCallback messageCallback, NameCallback nameCallback,
			PasswordCallback passwordCallback) throws LoginException {

		try {
			callbackHandler.handle(new Callback[] { confirmationCallback,
					messageCallback, nameCallback, passwordCallback });
		} catch( ThreadDeath death ) {
			  LoginException loginException = new LoginException();
			  loginException.initCause( death );
			  throw loginException;
		} catch (IOException e) {
			throw new LoginException();
		} catch (UnsupportedCallbackException e) {
			throw new LoginException();
		}

		if (confirmationCallback.getSelectedIndex() == ConfirmationCallback.CANCEL) {
			throw new LoginException();
		}

		try {  
		    String username = nameCallback.getName();  
		    String password = new String(passwordCallback.getPassword());
		  
		    passwordCallback.clearPassword();  
		  
		    if (!rdbmsValidate(username, password)) {
		    	login(confirmationCallback, messageCallback, nameCallback, passwordCallback);
		    }
		    user = username;
		}  
		catch (Exception ex) {  
		    throw new LoginException(ex.getMessage());  
		}
		return true;
	}
	
	private boolean rdbmsValidate(String username, String password) throws LoginException {
		try {
			String encryptedPassword = DbAccess.getPasswordForUser(username);
			return EncryptionUtil.authenticateBase64(password, encryptedPassword);
		} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
			e.printStackTrace();
			throw new LoginException();
		} catch (SQLException e) {
			e.printStackTrace();
			throw new LoginException();
		}
	}

	@Override
	public boolean logout() throws LoginException {
		if (!loggedin) {
			return false;
		}

		subject.getPrincipals().clear();
		subject.getPublicCredentials().clear();
		subject.getPrivateCredentials().clear();
		user = null;
		
		loggedin = false;
		return true;// TODO Auto-generated method stub
	}

	@Override
	public boolean commit() throws LoginException {
		String role = DbAccess.getRoleForUser(user);
		String[] permissions = DbAccess.getPermissionsForUser(user);

		subject.getPrincipals().add(getRolePrincipal(role));
		subject.getPrincipals().addAll(getPermissionPrincipals(permissions));
//		subject.getPrivateCredentials().add(
//				user.getCredentials().get("password"));

		loggedin = true;
		return true;
	}

	private RolePrincipal getRolePrincipal(String name) {
		return new RolePrincipal(name);
	}

	private List<PermissionPrincipal> getPermissionPrincipals(String[] permissions) {
		List<PermissionPrincipal> result = new ArrayList<>();
		for (String perm : permissions) {
			result.add(new PermissionPrincipal(perm));
		}
		return result;
	}
	
	@Override
	public boolean abort() throws LoginException {
		return false;
	}

}
